package com.immediatus.graphics.camera;

public class SmoothCamera extends ZoomCamera{

    private float _maxVelocityX;
    private float _maxVelocityY;
    private float _maxZoomFactorChange;

    private float _targetCenterX;
    private float _targetCenterY;

    private float _targetZoomFactor;

    public SmoothCamera(final float x_, final float y_, final float w_, final float h_, final float maxVelocityX_, final float maxVelocityY_, final float maxZoomFactorChange_){
        super(x_, y_, w_, h_);
        this._maxVelocityX = maxVelocityX_;
        this._maxVelocityY = maxVelocityY_;
        this._maxZoomFactorChange = maxZoomFactorChange_;

        this._targetCenterX = this.getCenterX();
        this._targetCenterY = this.getCenterY();

        this._targetZoomFactor = 1.0f;
    }

    @Override
    public void setCenter(final float x_, final float y_){
        this._targetCenterX = x_;
        this._targetCenterY = y_;
    }

    public void setCenterDirect(final float x_, final float y_){
        super.setCenter(x_, y_);
        this._targetCenterX = x_;
        this._targetCenterY = y_;
    }

    @Override
    public void setZoomFactor(final float zoomFactor_){
        if (this._targetZoomFactor != zoomFactor_){
            if (this._targetZoomFactor == this._zoomFactor){
                this._targetZoomFactor = zoomFactor_;

                this.onSmoothZoomStarted();
            } else{
                this._targetZoomFactor = zoomFactor_;
            }
        }
    }

    public void setZoomFactorDirect(final float zoomFactor_){
        if (this._targetZoomFactor != this._zoomFactor){
            this._targetZoomFactor = zoomFactor_;
            super.setZoomFactor(zoomFactor_);

            this.onSmoothZoomFinished();
        } else{
            this._targetZoomFactor = zoomFactor_;
            super.setZoomFactor(zoomFactor_);
        }
    }

    public void setMaxVelocityX(final float maxVelocityX_){
        this._maxVelocityX = maxVelocityX_;
    }

    public void setMaxVelocityY(final float maxVelocityY_){
        this._maxVelocityY = maxVelocityY_;
    }

    public void setMaxVelocity(final float maxVelocityX_, final float maxVelocityY_){
        this._maxVelocityX = maxVelocityX_;
        this._maxVelocityY = maxVelocityY_;
    }

    public void setMaxZoomFactorChange(final float maxZoomFactorChange_){
        this._maxZoomFactorChange = maxZoomFactorChange_;
    }

    protected void onSmoothZoomStarted(){
    }

    protected void onSmoothZoomFinished(){
    }

    @Override
    public void onUpdate(final float secondsElapsed_){
        super.onUpdate(secondsElapsed_);

        final float currentCenterX = this.getCenterX();
        final float currentCenterY = this.getCenterY();

        final float targetCenterX = this._targetCenterX;
        final float targetCenterY = this._targetCenterY;

        if (currentCenterX != targetCenterX || currentCenterY != targetCenterY){
            final float diffX = targetCenterX - currentCenterX;
            final float dX = this.limitToMaxVelocityX(diffX, secondsElapsed_);

            final float diffY = targetCenterY - currentCenterY;
            final float dY = this.limitToMaxVelocityY(diffY, secondsElapsed_);

            super.setCenter(currentCenterX + dX, currentCenterY + dY);
        }

        final float currentZoom = this.getZoomFactor();

        final float targetZoomFactor = this._targetZoomFactor;

        if (currentZoom != targetZoomFactor){
            final float absoluteZoomDifference = targetZoomFactor - currentZoom;
            final float zoomChange = this.limitToMaxZoomFactorChange(absoluteZoomDifference, secondsElapsed_);
            super.setZoomFactor(currentZoom + zoomChange);

            if (this._zoomFactor == this._targetZoomFactor){
                this.onSmoothZoomFinished();
            }
        }
    }

    private float limitToMaxVelocityX(final float value_, final float secondsElapsed_){
        if (value_ > 0){
            return Math.min(value_, this._maxVelocityX * secondsElapsed_);
        } else{
            return Math.max(value_, -this._maxVelocityX * secondsElapsed_);
        }
    }

    private float limitToMaxVelocityY(final float value_, final float secondsElapsed_){
        if (value_ > 0){
            return Math.min(value_, this._maxVelocityY * secondsElapsed_);
        } else{
            return Math.max(value_, -this._maxVelocityY * secondsElapsed_);
        }
    }

    private float limitToMaxZoomFactorChange(final float value_, final float secondsElapsed_){
        if (value_ > 0){
            return Math.min(value_, this._maxZoomFactorChange * secondsElapsed_);
        } else{
            return Math.max(value_, -this._maxZoomFactorChange * secondsElapsed_);
        }
    }
}
